using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware.UsbClient;
using System.Threading;

namespace Netduino_USB_HID_Library
{
    public static class USB_Port
    {
        const int WRITE_ENDPOINT = 1;
        const int MAXPOWER_MILLIAMPS = 200;
        const int HID_DESCRIPTOR_TYPE = 0x21;

        public static UsbStream stream;

        public static string SetUp()
        {
            UsbController[] usbControllers = UsbController.GetControllers();
            UsbController usbController;

            if (usbControllers.Length < 1)
                return "Error: No controllers found.";

            usbController = usbControllers[0];

            try
            {
                Configure(usbController);

                if (!usbController.Start())
                    return "Error: Could not start controller.";

                stream = usbController.CreateUsbStream(WRITE_ENDPOINT, UsbStream.NullEndpoint);
            }
            catch(Exception e)
            { return "Error:\n" + e.Message; }

            return "Success";
        }

        public static void Configure(UsbController usbController)
        {
            Configuration configuration = new Configuration();
            Configuration.DeviceDescriptor deviceDescriptor = new Configuration.DeviceDescriptor(0x22B1, 0xFFFF, 0x0100); // VID, PID, version -- if you change the PID (0xFFFF) be sure to change the VID.
            
            deviceDescriptor.bcdUSB = 0x0200;
            deviceDescriptor.iManufacturer = 0x01;   // string 1: manufacturer name
            deviceDescriptor.iProduct = 0x02;        // string 2: product name
            deviceDescriptor.iSerialNumber = 0x03;   // string 3: serial number
            deviceDescriptor.bDeviceClass = 0x00;
            deviceDescriptor.bDeviceSubClass = 0x00;
            deviceDescriptor.bDeviceProtocol = 0x00;
            deviceDescriptor.bMaxPacketSize0 = 0x08;

            byte[] hidClassDescriptorPayload = new byte[] 
            {
                0x01, 0x11,     // bcdHID (v1.01) || 0x01, 0x01, changed to 0x01, 0x11, 
                0x00,           // bCountryCode
                0x01,           // bNumDescriptors
                0x22,           // bDescriptorType (report)
                0x43, 0x00      // wDescriptorLength (report descriptor size in bytes)
            };

            // HID class descriptor
            Configuration.ClassDescriptor hidClassDescriptor = new Configuration.ClassDescriptor(HID_DESCRIPTOR_TYPE, hidClassDescriptorPayload);
            // write endpoint
            Configuration.Endpoint writeEndpoint = new Configuration.Endpoint(WRITE_ENDPOINT, Configuration.Endpoint.ATTRIB_Interrupt | Configuration.Endpoint.ATTRIB_Write);
            writeEndpoint.wMaxPacketSize = 16;   // packet size: 16 / 32 bytes
            writeEndpoint.bInterval = 10;         // interval: 10ms
            Configuration.UsbInterface usbInterface = new Configuration.UsbInterface(0, new Configuration.Endpoint[] { writeEndpoint });
            usbInterface.classDescriptors = new Configuration.ClassDescriptor[] { hidClassDescriptor };
            usbInterface.bInterfaceClass = 0x03;
            usbInterface.bInterfaceSubClass = 0x00;
            usbInterface.bInterfaceProtocol = 0x00; // keyboard

            // configuration descriptor
            Configuration.ConfigurationDescriptor configurationDescriptor = new Configuration.ConfigurationDescriptor(MAXPOWER_MILLIAMPS, new Configuration.UsbInterface[] { usbInterface });
            configurationDescriptor.bmAttributes = Configuration.ConfigurationDescriptor.ATTRIB_Base | Configuration.ConfigurationDescriptor.ATTRIB_SelfPowered;

            // HID report descriptor
            byte[] hidGenericReportDescriptorPayload = new byte[] 
            {
                0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
                0x09, 0x06,                    // USAGE (Keyboard)

                0xA1, 0x01,                    // COLLECTION (Application)

                0x85, 0x01,                    //   REPORT_ID (1)
                0x05, 0x07,                    //   USAGE PAGE (Keyboard)
                0x19, 0xE0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
                0x29, 0xE7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
                0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
                0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
                0x75, 0x01,                    //   REPORT_SIZE (1)
                0x95, 0x08,                    //   REPORT_COUNT (8)
                0x81, 0x02,                    //   INPUT (Data,Var,Abs)
                0x95, 0x01,                    //   REPORT_COUNT (1)
                0x75, 0x08,                    //   REPORT_SIZE (8)
                0x81, 0x01,                    //   INPUT (Const,Ary,Abs)
                0x95, 0x03,                    //   REPORT_COUNT (3)
                0x75, 0x01,                    //   REPORT_SIZE (1)
                0x05, 0x08,                    //   USAGE_PAGE (LEDs)
                0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
                0x29, 0x03,                    //   USAGE_MAXIMUM (Scroll Lock)
                0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)
                0x95, 0x01,                    //   REPORT_COUNT (1)
                0x75, 0x05,                    //   REPORT_SIZE (5)
                0x91, 0x01,                    //   Output (Cnst,Ary,Abs)
                0x95, 0x06,                    //   REPORT_COUNT (6)
                0x75, 0x08,                    //   REPORT_SIZE (8)
                0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
                0x26, 0xFF, 0x00,              //   LOGICAL_MAXIMUM (255)
                0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
                0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
                0x2A, 0xFF, 0x00,              //   USAGE_MAXIMUM (Keyboard Application, 255)
                0x81, 0x00,                    //   INPUT (Data,Ary,Abs)

                0xC0,                           // END_COLLECTION
            };

            Configuration.GenericDescriptor hidGenericReportDescriptor = new Configuration.GenericDescriptor(0x81, 0x2200, hidGenericReportDescriptorPayload);
            hidGenericReportDescriptor.bRequest = 0x06; // GET_DESCRIPTOR
            hidGenericReportDescriptor.wIndex = 0x00; // INTERFACE 0 (zero)

            Configuration.StringDescriptor manufacturerNameStringDescriptor = new Configuration.StringDescriptor(1, "Secet Labs LLC");
            Configuration.StringDescriptor productNameStringDescriptor = new Configuration.StringDescriptor(2, "Netduino Pad");
            Configuration.StringDescriptor serialNumberStringDescriptor = new Configuration.StringDescriptor(3, "1");
            Configuration.StringDescriptor displayNameStringDescriptor = new Configuration.StringDescriptor(4, "Netduino Pad");
            Configuration.StringDescriptor friendlyNameStringDescriptor = new Configuration.StringDescriptor(5, "Netduino Pad");

            configuration.descriptors = new Configuration.Descriptor[]
            {
                deviceDescriptor,
                configurationDescriptor, 
                manufacturerNameStringDescriptor, 
                productNameStringDescriptor,
                serialNumberStringDescriptor,
                displayNameStringDescriptor,
                friendlyNameStringDescriptor,
                hidGenericReportDescriptor
            };

            usbController.Configuration = configuration;

            if (usbController.ConfigurationError != UsbController.ConfigError.ConfigOK)
                return;
        }
    }
}
